/*
**********************************************************************
*                           The MIT License                          *
*                                                                    *
* Copyright (c) 2011 Flily Hsu                                       *
*                                                                    *
* Permission is hereby granted, free of charge, to any person        *
* obtaining a copy of this software and associated documentation     *
* files (the "Software"), to deal in the Software without            *
* restriction, including without limitation the rights to use, copy, *
* modify, merge, publish, distribute, sublicense, and/or sell copies *
* of the Software, and to permit persons to whom the Software is     *
* furnished to do so, subject to the following conditions:           *
*                                                                    *
* The above copyright notice and this permission notice shall be     *
* included in all copies or substantial portions of the Software.    *
*                                                                    *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,    *
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF *
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND              *
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT        *
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,       *
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, *
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER      *
* DEALINGS IN THE SOFTWARE.                                          *
**********************************************************************
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include "brainfuck.h"

#define CHECK_NEXT_ARGUMENT					\
	do{										\
		if( i >= argc - 1 )					\
		{									\
			miss_arg( argv[i] );			\
			exit( -1 );						\
		}									\
	}while( 0 )

void miss_arg( const char* arg )
{
	printf( "ERROR: argument to '%s' is missing\n", arg );
}

int paracmp( const char* param, int count, ... )
{
	va_list list;
	int retcode = -1;
	int i = 0;
	const char* cp = NULL;
	va_start( list, count );

	for( i = 0; i < count; i++ )
	{
		cp = va_arg( list, const char* );
		if( !strcmp( param, cp ) )
		{
			retcode = 0;
			break;
		}
	}

	va_end( list );
	return retcode;
}

void usage( const char* program_name )
{
	printf( "brainfuck interpreter and debugger\n" );
	printf( "%s [options] file\n", program_name );
	printf( "Options:\n" );
	printf( "    -(h)elp           Show this message.\n");
	printf( "    -(s)tack size     Size for loop stack.\n" );
	printf( "    -(m)emory size    Size for memory.\n" );
	printf( "    -(o)utput file    Redirect stdout to file.\n" );
	printf( "    -(i)nput file     Redirect stdin to file.\n" );
	printf( "    -(d)ebug          Running interpreter in debug mode.\n" );
	printf( "    -(ch)eck          Enable runtime check for memory and stack.\n" );
	printf( "    -(du)mp file      Dump memory into file after executing.\n" );
	
	exit( 1 );
}

void dump( brainfuck_vm vm, FILE* dumpfile )
{
	int i = 0;
	printf( "Begin dumping memory...\n" );
	
	for( i = 0; i < vm->memory_size; i++ )
	{
		if( 0 == i % 16 )
			fprintf( dumpfile, "0x%08x ", i );
		fprintf( dumpfile, "%02x ", vm->memory[i] );
		if( 15 == i % 16 )
			fprintf( dumpfile, "\n" );

		if( 0 == ( i + 1 ) % 1024 )
			printf( "\r%d(of %d) kbytes has dumpped.", ( i + 1 ) / 1024, vm->memory_size / 1024 );
	}
	fprintf( dumpfile, "\n" );
	printf( "\n" );
}

int main( int argc, char* argv[] )
{
	int retcode = BRAINFUCK_SUCCESS;
	char errbuf[BRAINFUCK_ERROR_BUFFER_SIZE] = "";
	int i = 0;
	int is_dump = 0;
	FILE* dumpfile = NULL;

	int mode = BRAINFUCK_NORMAL_MODE;
	FILE *code = NULL, *input = NULL, *output = NULL;
	int memory_size = DEFAULT_MEMORY_SIZE, stack_size = DEFAULT_STACK_SIZE;

	brainfuck_vm bfvm = NULL;

	if( argc <= 1 )
		usage( argv[0] );

	/* processing arguments */
	for( i = 1; i < argc; )
	{
		if( !paracmp( argv[i], 5, "-help", "-?", "-h", "/?", "--help" ) )
			usage( argv[0] );
		else if( !paracmp( argv[i], 2, "-debug", "-d" ) )
		{
			mode |= BRAINFUCK_DEBUG_MODE;
			i++;
		}
		else if( !paracmp( argv[i], 2, "-check", "-ch" ) )
		{
			mode |= BRAINFUCK_RUNTIME_CHECK;
			i++;
		}
		else if( !paracmp( argv[i], 2, "-output", "-o" ) )
		{
			CHECK_NEXT_ARGUMENT;
			if( NULL == ( output = fopen( argv[i+1], "wb" ) ) )
			{
				printf( "ERROR: can not open output file %s, redirect to stdout\n", argv[i+1] );
				output = stdout;
			}
			i += 2;
		}
		else if( !paracmp( argv[i], 2, "-input", "-i" ) )
		{
			CHECK_NEXT_ARGUMENT;
			if( NULL == ( input = fopen( argv[i+1], "rb" ) ) )
			{
				printf( "ERROR: can not open input file %s, redirect to stdin\n", argv[i+1] );
				input = stdin;
			}
			i += 2;
		}
		else if( !paracmp( argv[i], 2, "-stack", "-s" ) )
		{
			CHECK_NEXT_ARGUMENT;
			if( 0 >= ( stack_size = atoi( argv[i+1]) ) )
			{
				printf( "WARNING: invalid argument specified, stack size will set to default %d\n", DEFAULT_STACK_SIZE );
				stack_size = DEFAULT_STACK_SIZE;
			}
			i += 2;
		}
		else if ( !paracmp( argv[i], 2, "-memory", "-m" ) )
		{
			CHECK_NEXT_ARGUMENT;
			if( 0 >= ( memory_size = atoi( argv[i+1] ) ) )
			{
				printf( "WARNING: invalid argument specified, memory size will set to default %d\n", DEFAULT_MEMORY_SIZE );
				memory_size = DEFAULT_MEMORY_SIZE;
			}
			i += 2;
		}
		else if( !paracmp( argv[i], 2, "-dump", "-d" ) )
		{
			CHECK_NEXT_ARGUMENT;
			is_dump = 1;
			dumpfile = fopen( argv[i+1], "w" );
			if( NULL == dumpfile )
				dumpfile = stdout;
			i += 2;
		}
		else
		{
			code = fopen( argv[i], "rb" );
			if( NULL == code )
			{
				printf( "ERROR: can not open file %s\n", argv[i] );
				exit( -1 );
			}
			i++;
		}
	}

	bfvm  = brainfuck_build();
	if( NULL == bfvm )
	{
		printf( "Create interpreter instance error.\n" );
		exit( -1 );
	}

	brainfuck_ioredirect( bfvm, input, output );
	brainfuck_load( bfvm, code );
	brainfuck_setmode( bfvm, mode );

	do{
		retcode = brainfuck_init( bfvm, NULL, memory_size, NULL, stack_size );
		if( BRAINFUCK_SUCCESS != retcode )
			break;

		retcode = brainfuck_start( bfvm );
		if( BRAINFUCK_SUCCESS != retcode )
			break;

		retcode = brainfuck_run( bfvm );
	}while( 0 );

	if( BRAINFUCK_ERROR_BREAKPOINT_REACH == retcode )
	{
		brainfuck_debug_print( bfvm );
	}

	if( is_dump )
	{
		dump( bfvm, dumpfile );
		fclose( dumpfile );
	}

	retcode = brainfuck_destroy( bfvm, BRAINFUCK_FREE_ALL );
	if( BRAINFUCK_SUCCESS != retcode )
	{
		brainfuck_error_message( retcode, errbuf );
		printf( "%s\n", errbuf );
	}

	return 0;
}
